/*
  mapimage.js
  The MapImage object, repesents an instance of a single bound image map
*/

(function ($) {
  'use strict';

  var m = $.mapster,
    u = m.utils,
    ap = [],
    configCheckInterval = 50;
  /**
   * An object encapsulating all the images used by a MapData.
   */

  m.MapImages = function (owner) {
    this.owner = owner;
    this.clear();
  };

  m.MapImages.prototype = {
    constructor: m.MapImages,

    /* interface to make this array-like */

    slice: function () {
      return ap.slice.apply(this, arguments);
    },
    splice: function () {
      ap.slice.apply(this.status, arguments);
      var result = ap.slice.apply(this, arguments);
      return result;
    },

    /**
     * a boolean value indicates whether all images are done loading
     * @return {bool} true when all are done
     */
    complete: function () {
      return $.inArray(false, this.status) < 0;
    },

    /**
     * Save an image in the images array and return its index
     * @param  {Image} image An Image object
     * @return {int} the index of the image
     */

    _add: function (image) {
      var index = ap.push.call(this, image) - 1;
      this.status[index] = false;
      return index;
    },

    /**
     * Return the index of an Image within the images array
     * @param  {Image} img An Image
     * @return {int} the index within the array, or -1 if it was not found
     */

    indexOf: function (image) {
      return u.indexOf(this, image);
    },

    /**
     * Clear this object and reset it to its initial state after binding.
     */

    clear: function () {
      var me = this;

      if (me.ids && me.ids.length > 0) {
        $.each(me.ids, function (_, e) {
          delete me[e];
        });
      }

      /**
       * A list of the cross-reference IDs bound to this object
       * @type {string[]}
       */

      me.ids = [];

      /**
       * Length property for array-like behavior, set to zero when initializing. Array prototype
       * methods will update it after that.
       *
       * @type {int}
       */

      me.length = 0;

      /**
       * the loaded status of the corresponding image
       * @type {boolean[]}
       */

      me.status = [];

      // actually erase the images

      me.splice(0);
    },

    /**
     * Bind an image to the map and add it to the queue to be loaded; return an ID that
     * can be used to reference the
     *
     * @param {Image|string} image An Image object or a URL to an image
     * @param {string} [id] An id to refer to this image
     * @returns {int} an ID referencing the index of the image object in
     *                map_data.images
     */

    add: function (image, id) {
      var index,
        src,
        me = this;

      if (!image) {
        return;
      }

      if (typeof image === 'string') {
        src = image;
        image = me[src];
        if (typeof image === 'object') {
          return me.indexOf(image);
        }

        image = $('<img />').addClass('mapster_el').hide();

        index = me._add(image[0]);

        image
          .on('load.mapster', function (e) {
            me.imageLoaded.call(me, e);
          })
          .on('error.mapster', function (e) {
            me.imageLoadError.call(me, e);
          });

        image.attr('src', src);
      } else {
        // use attr because we want the actual source, not the resolved path the browser will return directly calling image.src

        index = me._add($(image)[0]);
      }
      if (id) {
        if (this[id]) {
          throw (
            id + ' is already used or is not available as an altImage alias.'
          );
        }
        me.ids.push(id);
        me[id] = me[index];
      }
      return index;
    },

    /**
     * Bind the images in this object,
     * @return {Promise} a promise that resolves when the images have finished loading
     */

    bind: function () {
      var me = this,
        promise,
        triesLeft = me.owner.options.configTimeout / configCheckInterval,
        /* A recursive function to continue checking that the images have been
               loaded until a timeout has elapsed */

        check = function () {
          var i;

          // refresh status of images

          i = me.length;

          while (i-- > 0) {
            if (!me.isLoaded(i)) {
              break;
            }
          }

          // check to see if every image has already been loaded

          if (me.complete()) {
            me.resolve();
          } else {
            // to account for failure of onLoad to fire in rare situations
            if (triesLeft-- > 0) {
              me.imgTimeout = window.setTimeout(function () {
                check.call(me, true);
              }, configCheckInterval);
            } else {
              me.imageLoadError.call(me);
            }
          }
        };

      promise = me.deferred = u.defer();

      check();
      return promise;
    },

    resolve: function () {
      var me = this,
        resolver = me.deferred;

      if (resolver) {
        // Make a copy of the resolver before calling & removing it to ensure
        // it is not called twice
        me.deferred = null;
        resolver.resolve();
      }
    },

    /**
     * Event handler for image onload
     * @param  {object} e jQuery event data
     */

    imageLoaded: function (e) {
      var me = this,
        index = me.indexOf(e.target);

      if (index >= 0) {
        me.status[index] = true;
        if ($.inArray(false, me.status) < 0) {
          me.resolve();
        }
      }
    },

    /**
     * Event handler for onload error
     * @param  {object} e jQuery event data
     */

    imageLoadError: function (e) {
      clearTimeout(this.imgTimeout);
      this.triesLeft = 0;
      var err = e
        ? 'The image ' + e.target.src + ' failed to load.'
        : 'The images never seemed to finish loading. You may just need to increase the configTimeout if images could take a long time to load.';
      throw err;
    },
    /**
     * Test if the image at specificed index has finished loading
     * @param  {int}  index The image index
     * @return {boolean} true if loaded, false if not
     */

    isLoaded: function (index) {
      var img,
        me = this,
        status = me.status;

      if (status[index]) {
        return true;
      }
      img = me[index];

      if (typeof img.complete !== 'undefined') {
        status[index] = img.complete;
      } else {
        status[index] = !!u.imgWidth(img);
      }
      // if complete passes, the image is loaded, but may STILL not be available because of stuff like adblock.
      // make sure it is.

      return status[index];
    }
  };
})(jQuery);
